type SerializeCallback<T> = (binaryWriter, data: T) => boolean;
type DeserializeCallback<T> = (binaryReader) => T;
type TryGetValueCallback<T> = (binaryReader, key: string, value: T) => boolean;

export abstract class GameFrameworkSerializer<T> {

    private readonly _serializeCallbacks: Map<number, SerializeCallback<T>>;
    private readonly _deserializeCallbacks: Map<number, DeserializeCallback<T>>;
    private readonly _tryGetValueCallbacks: Map<number, TryGetValueCallback<T>>;
    private _latestSerializeCallbackVersion: number;

    /// <summary>
    /// 初始化游戏框架序列化器基类的新实例。
    /// </summary>
    public constructor() {
        this._serializeCallbacks = new Map<number, SerializeCallback<T>>();
        this._deserializeCallbacks = new Map<number, DeserializeCallback<T>>();
        this._tryGetValueCallbacks = new Map<number, TryGetValueCallback<T>>();
        this._latestSerializeCallbackVersion = 0;
    }


    /// <summary>
    /// 注册序列化回调函数。
    /// </summary>
    /// <param name="version">序列化回调函数的版本。</param>
    /// <param name="callback">序列化回调函数。</param>
    public RegisterSerializeCallback(version: number, callback: SerializeCallback<T>): void {
        if (callback == null) {
            throw new Error("Serialize callback is invalid.");
        }

        this._serializeCallbacks[version] = callback;
        if (version > this._latestSerializeCallbackVersion) {
            this._latestSerializeCallbackVersion = version;
        }
    }

    /// <summary>
    /// 注册反序列化回调函数。
    /// </summary>
    /// <param name="version">反序列化回调函数的版本。</param>
    /// <param name="callback">反序列化回调函数。</param>
    public RegisterDeserializeCallback(version: number, callback: DeserializeCallback<T>): void {
        if (callback == null) {
            throw new Error("Deserialize callback is invalid.");
        }

        this._deserializeCallbacks[version] = callback;
    }

    /// <summary>
    /// 注册尝试从指定流获取指定键的值回调函数。
    /// </summary>
    /// <param name="version">尝试从指定流获取指定键的值回调函数的版本。</param>
    /// <param name="callback">尝试从指定流获取指定键的值回调函数。</param>
    public RegisterTryGetValueCallback(version: number, callback: TryGetValueCallback<T>): void {
        if (callback == null) {
            throw new Error("Try get value callback is invalid.");
        }

        this._tryGetValueCallbacks[version] = callback;
    }

    /// <summary>
    /// 序列化数据到目标流中。
    /// </summary>
    /// <param name="stream">目标流。</param>
    /// <param name="data">要序列化的数据。</param>
    /// <returns>是否序列化数据成功。</returns>
    public Serialize(stream: any[], data: T, version?: number): boolean {
        if (this._serializeCallbacks.size <= 0) {
            throw new Error("No serialize callback registered.");
        }
        if (version <= 0) {
            version = this._latestSerializeCallbackVersion;
        }
        let binaryWriter = [];
        let header: any[] = this.GetHeader();
        for (let i = 0; i < header.length; i++) {
            binaryWriter.push(header[i]);
        }
        binaryWriter.push(version);
        // for (let i = 0; i < stream.length; i++) {
        //     binaryWriter.push(stream[i]);
        // }
        let callback: SerializeCallback<T> = this._serializeCallbacks.get(version);
        if (callback == undefined) {
            throw new Error(`Serialize callback '${version}' is not exist.`)
        }
        return callback(binaryWriter, data);
    }

    public Deserialize(stream: []): T {
        let binaryReader = [];
        for (let i = 0; i < stream.length; i++) {
            binaryReader.push(stream[i]);
        }
        let header = this.GetHeader();
        if (header[0] != binaryReader[0] || header[1] != binaryReader[1] || header[2] != binaryReader[2]) {
            throw new Error(`Header is invalid, need '${header[0]}${header[1]}${header[2]}', current '${binaryReader[0]}${binaryReader[1]}${binaryReader[2]}'.`)
        }
        let version: number = binaryReader[3];
        let callback: DeserializeCallback<T> = this._deserializeCallbacks.get(version);
        return callback(binaryReader);
    }

    public TryGetValue(stream: any[], key: string, value: T): boolean {
        let binaryReader = [];
        for (let i = 0; i < stream.length; i++) {
            binaryReader.push(stream[i]);
        }
        value = null;
        let header = this.GetHeader();
        let header0 = binaryReader[0];
        let header1 = binaryReader[1];
        let header2 = binaryReader[2];
        if (header0 != header[0] || header1 != header[1] || header2 != header[2]) {
            return false;
        }
        let version: number = binaryReader[3];
        let callback: TryGetValueCallback<T> = this._tryGetValueCallbacks.get(version);
        return callback(binaryReader, key, value);
    }


    /// <summary>
    /// 获取数据头标识。
    /// </summary>
    /// <returns>数据头标识。</returns>
    protected abstract GetHeader(): any[];

}